﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using Xceed.Silverlight.Data.Stats;

namespace Xceed.Silverlight.Data
{
  [DebuggerDisplay( "ChildrenCount = {ChildrenCount}, Length = {Length}" )]
  internal class ListDataItemIndexGroupContainer : ListDataItemIndexNode
  {
    #region Constructor

    internal ListDataItemIndexGroupContainer(
      ListDataItemIndexConfiguration configuration,
      IEqualityComparer<StatFunction> statFunctionComparer,
      int level, 
      int startIndex,
      int length,
      IEnumerable<ListDataItemIndexGroup> childrenGroups,
      Func<LinkedListNode<object>, int, ListDataItemIndexNode> containerCreator )
      : base( configuration, statFunctionComparer, level, startIndex, length )
    {
      if( childrenGroups == null )
        throw new ArgumentNullException( "childrenGroups" );

      if( !childrenGroups.Any() )
        throw new ArgumentException( "At least one child group is required.", "childrenGroups" );

      if( containerCreator == null )
        throw new ArgumentNullException( "containerCreator" );

      if( statFunctionComparer == null )
        throw new ArgumentNullException( "statFunctionComparer" );

      m_childrenGroups = childrenGroups.ToList();
      m_readOnlyChildrenGroups = new ReadOnlyCollection<ListDataItemIndexGroup>( m_childrenGroups );
      m_containerCreator = containerCreator;
    }

    #endregion

    #region ChildrenGroups Property

    internal IList<ListDataItemIndexGroup> ChildrenGroups
    {
      get
      {
        return m_readOnlyChildrenGroups;
      }
    }

    private ReadOnlyCollection<ListDataItemIndexGroup> m_readOnlyChildrenGroups;
    private List<ListDataItemIndexGroup> m_childrenGroups;

    #endregion

    #region ChildrenCount Property

    private int ChildrenCount
    {
      get
      {
        return m_childrenGroups.Count;
      }
    }

    #endregion

    internal override void SlideLeft()
    {
      base.SlideLeft();

      foreach( ListDataItemIndexGroup group in m_childrenGroups )
      {
        group.Container.SlideLeft();
      }
    }

    internal override void SlideRight()
    {
      base.SlideRight();

      foreach( ListDataItemIndexGroup group in m_childrenGroups )
      {
        group.Container.SlideRight();
      }
    }

    internal override void Add( LinkedListNode<object> groupValue )
    {
      if( groupValue == null )
        throw new ArgumentNullException( "memberInfo" );

      int index;
      if( this.FindGroupIndex( groupValue.Value, false, false, out index ) )
      {
        m_childrenGroups[ index ].Container.Add( groupValue.Next );
      }
      else
      {
        // Determine the new group data item's index starting position.
        int indexStart;
        if( index < m_childrenGroups.Count )
        {
          indexStart = m_childrenGroups[ index ].Container.IndexStart;
        }
        else 
        {
          ListDataItemIndexNode lastContainer = m_childrenGroups.Last().Container;
          indexStart = lastContainer.IndexStart + lastContainer.Length;
        }

        // Create the whole branch that is containing the newly added item.
        ListDataItemIndexNode childContainer = m_containerCreator.Invoke( groupValue.Next, indexStart );
        ListDataItemIndexGroup childGroup = new ListDataItemIndexGroup( groupValue.Value, childContainer );

        m_childrenGroups.Insert( index, childGroup );
      }

      // We must count the newly added item has part of the current group
      // or one of its children.
      this.Grow();

      // Every child group that comes after the group that has the newly
      // added item must slide their starting index to the right.
      for( int i = index + 1; i < m_childrenGroups.Count; i++ )
      {
        m_childrenGroups[ i ].Container.SlideRight();
      }
    }

    internal override bool Remove( int index )
    {
      // Find out the index of the child container which contains
      // the removed item.
      int childIndex = 0;
      while( childIndex < m_childrenGroups.Count )
      {
        ListDataItemIndexNode childNode = m_childrenGroups[ childIndex ].Container;
        int indexInNode = index - childNode.IndexStart;

        if( ( indexInNode >= 0 ) && ( indexInNode < childNode.Length ) )
          break;

        childIndex++;
      }

      Debug.Assert( childIndex < m_childrenGroups.Count );

      int slideIndex;

      // An empty child container must be removed from the current group.
      if( m_childrenGroups[ childIndex ].Container.Remove( index ) )
      {
        m_childrenGroups.RemoveAt( childIndex );

        // The current group is now empty, it must be removed from the index.
        if( m_childrenGroups.Count <= 0 )
          return true;

        slideIndex = childIndex;
      }
      else
      {
        slideIndex = childIndex + 1;
      }

      // We must not count the newly removed item that was part of the current group
      // or one of its children.
      this.Shrink();

      // Every child group that comes after the group that had the newly
      // removed item must slide their starting index to the left.
      for( int i = slideIndex; i < m_childrenGroups.Count; i++ )
      {
        m_childrenGroups[ i ].Container.SlideLeft();
      }

      return false;
    }

    internal override int GetDataItemIndex(
      LinkedListNode<object> groupValue,
      bool reverseOrder,
      bool skipStartingItem )
    {
      // We are starting in this group.
      if( groupValue == null )
      {
        if( skipStartingItem )
          return ( reverseOrder )
                   ? this.IndexStart - 1
                   : this.IndexStart + this.Length;

        return ( reverseOrder )
                 ? this.IndexStart + this.Length - 1
                 : this.IndexStart;
      }

      // Find the group that match.
      int index;
      if( this.FindGroupIndex( groupValue.Value, reverseOrder, skipStartingItem, out index ) )
        return m_childrenGroups[ index ].Container.GetDataItemIndex( groupValue.Next, reverseOrder, skipStartingItem );

      // Get the closest item to the target.
      int dataItemIndex;
      if( index < m_childrenGroups.Count )
      {
        dataItemIndex = m_childrenGroups[ index ].Container.IndexStart;
      }
      // The target item is out of bound.
      else
      {
        dataItemIndex = this.IndexStart + this.Length;
      }

      if( reverseOrder )
      {
        dataItemIndex -= 1;
      }

      return dataItemIndex;
    }

    internal override bool GetGroupCount( LinkedListNode<int> groupCount )
    {
      // The current level must not be counted.
      if( groupCount == null )
        return false;

      LinkedListNode<int> nextGroupCount = groupCount.Next;
      bool isCounted = ( nextGroupCount == null );

      if( !isCounted )
      {
        foreach( ListDataItemIndexGroup group in m_childrenGroups )
        {
          // We must count the current group if one of it's child group
          // is counted.
          if( group.Container.GetGroupCount( nextGroupCount ) )
          {
            groupCount.Value += 1;
            isCounted = true;
          }
        }
      }
      else
      {
        groupCount.Value += m_childrenGroups.Count;
      }

      return isCounted;
    }

    internal override bool GetGroupCount(
      LinkedListNode<int> groupCount,
      LinkedListNode<object> groupValue )
    {
      // The current level must not be counted.
      if( groupCount == null )
        return false;

      LinkedListNode<int> nextGroupCount = groupCount.Next;
      bool isCounted = ( nextGroupCount == null );

      if( !isCounted )
      {
        // All items on this level must be counted.
        if( groupValue == null )
          return this.GetGroupCount( groupCount );

        // Find the group that match or is the closest to the target value.
        int index;
        if( this.FindGroupIndex( groupValue.Value, false, false, out index ) )
        {
          // Check if one of the children of the current group is counted.
          isCounted = m_childrenGroups[ index ].Container.GetGroupCount( nextGroupCount, groupValue.Next );
        }
      }

      if( isCounted )
      {
        groupCount.Value += 1;
      }

      return isCounted;
    }

    internal override bool GetGroupCount(
      LinkedListNode<int> groupCount,
      LinkedListNode<object> groupValue,
      bool reverseOrder,
      bool skipStartingItem )
    {
      // The current level must not be counted.
      if( groupCount == null )
        return false;

      // All items on this level must be counted.
      if( groupValue == null )
        return this.GetGroupCount( groupCount );

      // Find the group that match or is the closest to the target value.
      int index;
      bool useNextValue = this.FindGroupIndex( groupValue.Value, reverseOrder, skipStartingItem, out index );

      LinkedListNode<int> nextGroupCount = groupCount.Next;
      bool isCounted = false;

      // Count the current group only if at least one of its child group is counted.
      if( nextGroupCount == null )
      {
        groupCount.Value += ( reverseOrder )
                              ? ( index + 1 )
                              : ( m_childrenGroups.Count - index );
        isCounted = true;
      }
      else
      {
        int step = ( reverseOrder ) ? -1 : 1;
        LinkedListNode<object> nextValue = ( useNextValue ) ? groupValue.Next : ( LinkedListNode<object> )null;

        for( int i = index; i >= 0 && i < m_childrenGroups.Count; i += step )
        {
          if( m_childrenGroups[ i ].Container.GetGroupCount( nextGroupCount, nextValue, reverseOrder, skipStartingItem ) )
          {
            groupCount.Value += 1;
            isCounted = true;
          }
          nextValue = null;
        }
      }

      return isCounted;
    }

    internal override bool GetGroupCount(
      LinkedListNode<int> groupCount,
      LinkedListNode<object> filterGroupValue,
      LinkedListNode<object> groupValue,
      bool reverseOrder,
      bool skipStartingItem )
    {
      // The current level must not be counted.
      if( groupCount == null )
        return false;

      if( filterGroupValue == null )
        return this.GetGroupCount( groupCount, groupValue, reverseOrder, skipStartingItem );

      int index;
      bool found = this.FindGroupIndex( filterGroupValue.Value, reverseOrder, skipStartingItem, out index );

      // The group doesn't exists.
      if( !found )
        return false;

      LinkedListNode<object> nextGroupValue = ( groupValue != null ) ? groupValue.Next : null;

      if( groupValue != null )
      {
        int startIndex;
        this.FindGroupIndex( groupValue.Value, reverseOrder, false, out startIndex );

        // The starting group has no impact on the group count.
        if( ( ( !reverseOrder ) && ( startIndex > index ) )
          || ( ( reverseOrder ) && ( startIndex < index ) ) )
          return false;

        if( index != startIndex )
        {
          nextGroupValue = null;
          skipStartingItem = false;
        }
      }

      if( ( index >= 0 ) && ( index < m_childrenGroups.Count ) )
      {
        if( m_childrenGroups[ index ].Container.GetGroupCount(
              groupCount.Next,
              nextGroupValue,
              reverseOrder,
              skipStartingItem ) )
        {
          groupCount.Value += 1;

          return true;
        }
      }

      return false;
    }

    internal override void ClearStatResults()
    {
      foreach( ListDataItemIndexGroup childGroup in this.ChildrenGroups )
      {
        childGroup.Container.ClearStatResults();
      }

      base.ClearStatResults();
    }

    internal override StatResult GetStatValue(
      StatFunction statFunction,
      LinkedListNode<object> groupValue )
    {
      if( groupValue != null )
      {
        int index;
        if( !this.FindGroupIndex( groupValue.Value, false, false, out index ) )
          return null;

        ListDataItemIndexNode childNode = this.ChildrenGroups[ index ].Container;

        return childNode.GetStatValue( statFunction, groupValue.Next );
      }
      else
      {
        return base.GetStatValue( statFunction, groupValue );
      }
    }

    internal override void SetStatValue(
      StatFunction statFunction,
      LinkedListNode<object> groupValue,
      StatResult statResult )
    {
      if( groupValue != null )
      {
        int index;
        if( !this.FindGroupIndex( groupValue.Value, false, false, out index ) )
          Debug.Assert( false );

        ListDataItemIndexNode childNode = this.ChildrenGroups[ index ].Container;

        childNode.SetStatValue( statFunction, groupValue.Next, statResult );
      }
      else
      {
        base.SetStatValue( statFunction, groupValue, statResult );
      }
    }

    private bool FindGroupIndex(
      object groupValue,
      bool reverseOrder,
      bool skipStartingItem,
      out int index )
    {
      // Find the item that match or is the closest to the target value.
      ListDataItemIndexGroup targetGroup = new ListDataItemIndexGroup( groupValue, null );
      IComparer<ListDataItemIndexGroup> comparer = new GroupComparer( this.ItemComparer );
      index = m_childrenGroups.BinarySearch( targetGroup, comparer );

      // The item was found.
      if( index >= 0 )
        return true;

      // Find out the position of the group if it would have exist.
      index = ~index;

      return false;
    }

    #region Private Fields

    private Func<LinkedListNode<object>, int, ListDataItemIndexNode> m_containerCreator;

    #endregion

    #region Private GroupComparer Nested Type

    private class GroupComparer : IComparer<ListDataItemIndexGroup>
    {
      internal GroupComparer( IComparer comparer )
      {
        if( comparer == null )
          throw new ArgumentNullException( "comparer" );

        m_comparer = comparer;
      }

      public int Compare( ListDataItemIndexGroup x, ListDataItemIndexGroup y )
      {
        object firstValue = x.Value;
        object secondValue = y.Value;

        return m_comparer.Compare( firstValue, secondValue );
      }

      private IComparer m_comparer;
    }

    #endregion
  }
}
